﻿/*
 * Rappelz: Endless Odyssey - The first free open source Rappelz server emulator
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data;
using Rappelz.GameServer;
using MiscUtil.Threading;
using System.Threading;

namespace Rappelz.GameServer.Database
{
    public delegate void DatabaseProcess(object data);

    public class GameDBManager
    {
#region Enums
        /// <summary>
        /// Datbase type enum
        /// </summary>
        public enum DatabaseType : int
        {
            SQLServer = 0,
            MySql = 1,
            PostgreSql = 2,
        }
#endregion

#region Private Variables
        private String m_szConnection;
        public DbProviderFactory m_factory;
        private DbConnection m_connection;
        private DbCommandBuilder m_builder;
        private string _parameterMarkerFormat;
#endregion

#region Protected Variables
        /// <summary>
        /// A pool of threads for our database processing
        /// </summary>
        protected CustomThreadPool m_dbThreadPool;

        /// <summary>
        /// The instance!
        /// </summary>
        protected static GameDBManager m_instance;
#endregion

#region Properties
        /// <summary>
        /// Returns the instance
        /// </summary>
        public static GameDBManager Instance
        {
            get { return m_instance; }
        }

        /// <summary>
        /// Returns the database thread pool
        /// </summary>
        public CustomThreadPool DBThreadPool
        {
            get { return m_dbThreadPool; }
        }

        /// <summary>
        /// Returns the DBFactory object
        /// </summary>
        public DbProviderFactory DBFactory
        {
            get { return m_factory; }
        }
#endregion

#region Instance Construction
        /// <summary>
        /// Creates our GameDBManager instance
        /// </summary>
        public static void CreateInstance(Config.ConfigDb config)
        {
            if (m_instance == null)
                m_instance = new GameDBManager(config);
        }

        /// <summary>
        /// Constructs an AuroraServer object
        /// </summary>
        public GameDBManager(Config.ConfigDb config)
        {
            m_dbThreadPool = new CustomThreadPool("Database Threads");
            m_dbThreadPool.SetMinMaxThreads(config.dbThreadCount, config.dbThreadCount);
            m_dbThreadPool.StartMinThreads();

            m_szConnection = config.dbConnStr;

            Globals.Log.Debug("New Database connection: DbType: \"{0}\", ConnectionString: \"{1}\" managed by {2} thread(s)", config.dbType, config.dbConnStr, config.dbThreadCount);

            try
            {
                switch (config.dbType)
                {
                    case DatabaseType.SQLServer:
                        // Test using SqlClient
                        m_factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
                        break;

                    default:
                        throw new System.ArgumentOutOfRangeException("config.dbType", config.dbType.ToString(), "Database Type is no defined, can't connect to database.");
                }

                if (m_factory != null)
                {
                    m_connection = m_factory.CreateConnection();
                    m_connection.ConnectionString = m_szConnection;

                    m_builder = m_factory.CreateCommandBuilder();

                    if (m_connection != null)
                    {
                        m_connection.Open();
                        using (DataTable tbl = m_connection.GetSchema(DbMetaDataCollectionNames.DataSourceInformation))
                        {
                            _parameterMarkerFormat = tbl.Rows[0][DbMetaDataColumnNames.ParameterMarkerFormat] as string;
                        }
                        m_connection.Close();
                    }
                    if (String.IsNullOrEmpty(_parameterMarkerFormat))
                        _parameterMarkerFormat = "{0}";

                }
                else
                {
                    throw new System.Exception("Database Provider not available, can't connect to database.");
                }
                //formLoginServer.Instance.UpdateDatabaseStatus("Connected");
            }
            catch (System.Exception ex)
            {
                Globals.Log.Fatal("Error Connecting To Database! {0}", ex);
                //formLoginServer.Instance.UpdateDatabaseStatus("Failed");
            }

        }
#endregion

        /// <summary>
        /// Creates a connection to our database
        /// </summary>
        public DbConnection CreateConnection()
        {
            DbConnection c = DBFactory.CreateConnection();
            c.ConnectionString = m_szConnection;
            return c;
        }

        /// <summary>
        /// Creates a command for our database
        /// </summary>
        public static DbCommand CreateCommand()
        {
            DbCommand c = GameDBManager.Instance.DBFactory.CreateCommand();
            return c;
        }

        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateParameter(string parameterName, out string parameterMarker)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });

            parameterMarker = String.Format(System.Globalization.CultureInfo.InvariantCulture,
                _parameterMarkerFormat, param.ParameterName);

            return param;
        }

        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateParameter(string parameterName)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });

            return param;
        }

        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateInParameter(DbCommand cmd, string parameterName, DbType type, object value)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });
            param.DbType = type;
            param.Direction = ParameterDirection.Input;
            param.Value = value;
            cmd.Parameters.Add(param);
            return param;
        }

        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateInParameter(DbCommand cmd, string parameterName, DbType type)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });
            param.DbType = type;
            param.Direction = ParameterDirection.Input;
            cmd.Parameters.Add(param);
            return param;
        }


        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateOutParameter(DbCommand cmd, string parameterName, DbType type)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });
            param.DbType = type;
            if (type == DbType.String)
                param.Value = "";
            param.Direction = ParameterDirection.Output;
            cmd.Parameters.Add(param);
            return param;
        }

        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateOutParameter(DbCommand cmd, string parameterName, DbType type, int size)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });
            param.DbType = type;
//            if (type == DbType.String)
  //              param.Value = "";
            param.Direction = ParameterDirection.Output;
            param.Size = size;
            cmd.Parameters.Add(param);
            return param;
        }

        /// <summary>
        /// Creates a parameter formatted for the database in use
        /// </summary>
        public DbParameter CreateResultParameter(DbCommand cmd, string parameterName, DbType type)
        {
            DbParameter param = m_factory.CreateParameter();

            param.ParameterName = (string)typeof(DbCommandBuilder).InvokeMember("GetParameterName",
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod |
                System.Reflection.BindingFlags.NonPublic, null, m_builder, new object[] {
                parameterName });
            param.DbType = type;
            param.Direction = ParameterDirection.ReturnValue;
            cmd.Parameters.Add(param);
            return param;
        }

        /// <summary>
        /// Adds a Work Item for our database thread pool
        /// </summary>
        public void WaitUntilEmpty()
        {
            while(DBThreadPool.QueueLength != 0 || DBThreadPool.WorkingThreads != 0)
            {
                Thread.Sleep(20);
            }
        }

        /// <summary>
        /// Adds a Work Item for our database thread pool
        /// </summary>
        public void Push(Delegate proc, object data)
        {
            DBThreadPool.AddWorkItem(proc, data);
        }

        /// <summary>
        /// Adds a Work Item for our database thread pool
        /// </summary>
        public void Push(object data)
        {
            DatabaseProcess p = GameDBManager.dbProcess;
            DBThreadPool.AddWorkItem(p, data);
        }

        /// <summary>
        /// Callback for our worker threads
        /// </summary>
        public static void dbProcess(object data)
        {
            try
            {
                IDBProc db = data as IDBProc;
                db.onProcess();
                db.RunCB();
            }
            catch
            {

            }
        }

    }
}
